一覧に戻る

Cloudflare Containersでchiitilerを動かしてみる

#cloudflare

はじめに

Cloudflareがついにコンテナ実行環境を提供しはじめたので試す。単に動かすだけなら公式のテンプレや既存の記事を参照すればよい。

https://developers.cloudflare.com/containers/get-started/

https://zenn.dev/774u64/articles/1e351b11f21928

それだけでは面白くないので、本記事では私がメンテナンスしているラスタータイルサーバー「chiitiler」のコンテナイメージを動かすことを目標にトライしてみる。 https://github.com/Kanahiro/chiitiler

chiitilerの運用について

https://spatialty-io.github.io/chiitiler-demo/ chiitilerはデモサイトを公開していて、AWS Lambda + Lambda Web Adapterで動作している。もちろんAmazon ECSなどの「一般的な」コンテナ環境でもよく動作するが、chiitilerはAWS Lambda「でも」よく動くようにチューニングされていたりする。すなわち揮発性のインスタンスでいかにパフォーマンスを落とさないか、ということ。Cloudflare ContainersがAWS Lambdaくらいナイスなコンテナ環境なら嬉しい。

想定手順

ドキュメントや記事を斜め読みした状態での想定手順

  1. Cloudflareのコンテナレジストリにコンテナイメージをデプロイする
  2. Cloudflare WorkersからBindings経由でアクセスする

コンテナイメージのデプロイ

冒頭のドキュメントや記事では、Dockerfileを参照する方法が紹介されているが、ビルド済みイメージをデプロイ出来た方が楽ちん。ドキュメントを読むと、もちろんその方法もあった。 https://developers.cloudflare.com/containers/image-management/

# chiitilerのコンテナイメージはghcr.ioで公開しているので、それをpull->pushする
docker pull <public-image>
docker tag <public-image> <image>:<tag>
wrangler containers push <image>:<tag>

なお現状はamd64アーキテクチャのイメージのみサポートしている模様。ゆえに公開済みイメージからlinux/amd64のものを選択してpull、雑にタグをつけてpush。 registry.cloudflare.com/<uuid>/chiitiler:amdlatestというURIとなった。

デプロイしたコンテナイメージを利用したWorkersを実装する

まずはGetting Startedで利用しているテンプレを眺めてみる。Goで書かれたREST-APIをDockerfileで固めてデプロイする形式。コンテナイメージの方はあんまり細かいことは考えなくてよさそう。Workersの部分が本質。

import { Container, getContainer, getRandom } from "@cloudflare/containers";
import { Hono } from "hono";

export class MyContainer extends Container<Env> {
	// Port the container listens on (default: 8080)
	defaultPort = 8080;
	// Time before container sleeps due to inactivity (default: 30s)
	sleepAfter = "2m";
	// Environment variables passed to the container
	envVars = {
		MESSAGE: "I was passed in via the container class!",
	};
	// Optional lifecycle hooks
	override onStart() {
		console.log("Container successfully started");
	}
	
	override onStop() {
		console.log("Container successfully shut down");
	}
	override onError(error: unknown) {
		console.log("Container error:", error);
	}
}

// Create Hono app with proper typing for Cloudflare Workers
const app = new Hono<{
	Bindings: Env;
}>();

  

// Home route with available endpoints
app.get("/", (c) => {
	return c.text(
		"Available endpoints:\n" +
		"GET /container/<ID> - Start a container for each ID with a 2m timeout\n" +
		"GET /lb - Load balance requests over multiple containers\n" +
		"GET /error - Start a container that errors (demonstrates error handling)\n" +
		"GET /singleton - Get a single specific container instance",
	);
});

// Route requests to a specific container using the container ID
app.get("/container/:id", async (c) => {
	const id = c.req.param("id");
	const containerId = c.env.MY_CONTAINER.idFromName(`/container/${id}`);
	const container = c.env.MY_CONTAINER.get(containerId);
	return await container.fetch(c.req.raw);
});

// Demonstrate error handling - this route forces a panic in the container
app.get("/error", async (c) => {
	const container = getContainer(c.env.MY_CONTAINER, "error-test");
	return await container.fetch(c.req.raw);
});

// Load balance requests across multiple containers
app.get("/lb", async (c) => {
	const container = await getRandom(c.env.MY_CONTAINER, 3);
	return await container.fetch(c.req.raw);
});

// Get a single container instance (singleton pattern)
app.get("/singleton", async (c) => {
	const container = getContainer(c.env.MY_CONTAINER);	
	return await container.fetch(c.req.raw);
});

export default app;

ポイントはふたつで、Containerクラスの定義と、Workersからコンテナを呼び出す方法。

Containerクラスの定義

Workersのコード上に、そのコンテナをどう起動するのか直接書いてしまう仕様。ここではMyContainerという名前でexportしていて、この文字列をwranglare.jsoncに設定するという寸法。chiitilerはデフォルトで利用するポートは3000なので、そうしてやる。あと環境変数をいい感じに。

Workersのアプリケーションコード

サンプルコードには、コンテナを触るためのいくつかの方法が示されていて、idFromName()getContainer()getRandom()がある。今回はインスタンスを複数建ててみたいので、いい感じにロードバランシングしてほしい。ただし…

Currently, routing requests to one of many interchangeable Container instances is accomplished with the `getRandom` helper.

This is temporary — we plan to add native support for latency-aware autoscaling and load balancing in the coming months.

https://developers.cloudflare.com/containers/get-started/ 「いい感じのロードバランシング」は鋭意開発中という感じで、現状でそういうことがしたければgetRandom()を用いることになる模様。ということはつまり、並列稼働してる各インスタンスとコミュニケーションする方法があるということで、これはAWS LambdaやAmazon ECSではちょっと経験したことがない。

もう一点、サンプルコードはHonoを用いてリクエストのパスごとに異なる処理が実装されているが、chiitilerコンテナはそれ自体がいくつかのエンドポイントを実装していて、各ルートをHonoアプリケーションに実装するのはナンセンスである。なのでfetchを直接利用してやる。最終的なアプリケーションコードは下記のとおり。

import { Container, getRandom } from '@cloudflare/containers';

export class MyContainer extends Container<Env> {
	// Port the container listens on (default: 8080)
	defaultPort = 3000;
	// Time before container sleeps due to inactivity (default: 30s)
	sleepAfter = '2m';
	// Environment variables passed to the container
	envVars = {
		CHIITILER_DEBUG: 'true',
		CHIITILER_CACHE_METHOD: 'file',
	};
	
	// Optional lifecycle hooks
	override onStart() {
		console.log('Container successfully started');
	}
	
	override onStop() {
		console.log('Container successfully shut down');
	}
	
	override onError(error: unknown) {
		console.log('Container error:', error);
	}
}

  

export default {
	async fetch(
		request: Request,
		env: Env,
		ctx: ExecutionContext,
	): Promise<Response> {
		return (await getRandom(env.MY_CONTAINER, 2)).fetch(request);
	},
};

wrangler.jsonc

最後にwrangler.jsoncのようす。

"containers": [
	{
		"class_name": "MyContainer", // exportしたContainerクラスの名前
		"instance_type": "standard",
		"image": "registry.cloudflare.com/<uuid>/chiitiler:amdlatest",
		"max_instances": 10
	}
],

デプロイする

npx wrangler deploy

これによりWorkersがデプロイされる。chiitilerのデバッグページ(/debug)を開いてみる。 /debugページで地図画像がレンダリングされた、勝利。パフォーマンスも想像よりは悪くないけど良くはない、0.5vCPUしか使えないのがかなり影響していそう。

感想

Workersらしくユニークな操作・実装でコンテナイメージを起動することが出来た。デプロイまでの手軽さはさすがCloudflare。ではこれを使っていくのかというと、まずは料金を見てみる。

MemoryCPUDisk
FreeN/AN/A
Workers Paid25 GiB-hours/month included
+$0.0000025 per additional GiB-second
375 vCPU-minutes/month
+ $0.000020 per additional vCPU-second
200 GB-hours/month
+$0.00000007 per additional GB-second
https://developers.cloudflare.com/containers/pricing/

現在はインスタンスタイプは3種類から選べて、一番リッチなstandardは4GB RAM、0.5vCPU、4GB Disk(メモリの割にCPUが弱すぎる…)。一番高そうなMemoryで計算してみると、standardを1インスタンス起動させ続けると、4000JPY/monthとなる(はず、計算が正しければ)。さてこれが安いかというと、そこまで安くないかなと思った。ただCloudflareはエグレス無料という強みがあるので、目的によっては安く済む可能性はある。有望なユースケースは、Workersベースのアプリケーションで、workerdでは出来ないことをしたいときに使うコンテナ環境、かなと思う。 chiitilerを動かす環境としてはまだ厳しいけども、思っていたよりは普通に動作したので、今後の進化に期待したいところ。